#include "StdAfx.h"

#include <algorithm>

#include <wx/app.h>
#include <wx/msgdlg.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
#include <wx/dirdlg.h>
#include <wx/settings.h>
#include <wx/stdpaths.h>
#include <wx/ipc.h>
#include <wx/cmdline.h>
#include <wx/log.h>

#include "Serialization.h"
#include "yasli/TextIArchive.h"
#include "yasli/TextOArchive.h"
#include "yasli/Serializer.h"
#include "yasli/Files.h"
#include "yasli/MemoryWriter.h"

#include "MainFrame.h"
#include "ClassTree.h"
#include "TagStorage.h"
#include "TagSourceDirectory.h"

#include "property-tree/PropertyDialog.h"
#include "Preferences.h"

#include "EditorPreferences.h"
#include "TagPreferences.h"
#include "version.h"

class SymbolTextCtrl : public wxTextCtrl{
public:
	SymbolTextCtrl(wxWindow* parent, int id)
	: wxTextCtrl(parent, id, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER)
	{
	}

	void serialize(Archive& ar)
	{
		std::string value = static_cast<const char*>(GetValue().utf8_str());
		ar(value, "value");
		if(ar.isInput())
			SetValue(wxString(value.c_str(), wxConvUTF8));
	}
protected:

	void onSetFocus(wxFocusEvent& event)
	{
		event.Skip();
		SetSelection(-1, -1);
	}

	void onKeyDown(wxKeyEvent& event)
	{
		if(event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_TAB || event.GetKeyCode() == WXK_PAGEDOWN){
			wxKeyEvent newEvent = event;
			wxWindow* parent = GetParent();
			newEvent.SetEventObject(parent);
			newEvent.SetId(parent->GetId());
			parent->GetEventHandler()->ProcessEvent(newEvent);
		}
		else
			event.Skip();
	}

	DECLARE_EVENT_TABLE()

};

BEGIN_EVENT_TABLE(SymbolTextCtrl, wxTextCtrl)
	EVT_KEY_DOWN(SymbolTextCtrl::onKeyDown)
	EVT_SET_FOCUS(SymbolTextCtrl::onSetFocus)
END_EVENT_TABLE()


// ---------------------------------------------------------------------------

void handleException(ErrorGeneric& exception){
    MemoryWriter msg;
    msg << "Exception in " << exception.functionName()
        << "(), " << exception.fileName() << ": " << exception.line() << " line\n"; 
    msg << "\t" << exception.what();

    wxMessageBox(wxString(msg.c_str(), wxConvUTF8),
                 wxT("Exception caughts"), wxOK | wxICON_ERROR);
}

enum {
    ID_FRAME = 10000,

	ID_SYMBOL_TEXT,
	ID_MEMBER_TEXT,
	ID_CLASS_TREE,

    // menus
    IDM_FILE_OPEN,
    IDM_FILE_OPEN_DIRECTORY,
    IDM_FILE_PREFERENCES,
    IDM_FILE_EXIT,

	IDM_DEBUG_TEST1,
	IDM_DEBUG_TEST2,

    IDM_HELP_ABOUT
};

IMPLEMENT_CLASS(MainFrame, wxFrame)
BEGIN_EVENT_TABLE(MainFrame, wxFrame)

    EVT_CLOSE(MainFrame::onClose)
    EVT_IDLE(MainFrame::onIdle)
	EVT_KEY_DOWN(MainFrame::onKeyDown)

	EVT_TIMER(wxID_ANY, MainFrame::onTimer)
	//
	EVT_TEXT(ID_SYMBOL_TEXT, MainFrame::onSymbolTextChanged)
	EVT_COMMAND(ID_SYMBOL_TEXT, wxEVT_COMMAND_TEXT_ENTER, MainFrame::onSymbolTextEnter)

	EVT_TEXT(ID_MEMBER_TEXT, MainFrame::onSymbolTextChanged)
	EVT_COMMAND(ID_MEMBER_TEXT, wxEVT_COMMAND_TEXT_ENTER, MainFrame::onSymbolTextEnter)

	EVT_TREE_ITEM_ACTIVATED(ID_CLASS_TREE, MainFrame::onSymbolSelected)
	///

    EVT_MENU(IDM_FILE_OPEN, MainFrame::onFileOpen)
    EVT_MENU(IDM_FILE_OPEN_DIRECTORY, MainFrame::onFileOpenDirectory)
    EVT_MENU(IDM_FILE_PREFERENCES, MainFrame::onFilePreferences)
    EVT_MENU(IDM_FILE_EXIT, MainFrame::onFileExit)

	EVT_MENU(IDM_DEBUG_TEST1, MainFrame::onDebugTest1)
	EVT_MENU(IDM_DEBUG_TEST2, MainFrame::onDebugTest2)

    EVT_MENU(IDM_HELP_ABOUT, MainFrame::onHelpAbout)

END_EVENT_TABLE()

void MainFrame::onKeyDown(wxKeyEvent& event)
{
	if(event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_PAGEDOWN)
		tree_->SetFocus();
	if(event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_HOME || event.GetKeyCode() == WXK_PAGEUP)
		symbolText_->SetFocus();
	if(event.GetKeyCode() == WXK_TAB){
		if(FindFocus() == symbolText_)
			memberText_->SetFocus();
		else
			symbolText_->SetFocus();
	}
}

MainFrame::MainFrame()
: tree_(0)
, directoryWatch_(0)
, PreferencesBlock("window", false)
{
    Create(0, ID_FRAME, wxT("VimKick"), wxDefaultPosition,
		   wxDefaultSize, wxCAPTION | wxSYSTEM_MENU | 
		   wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxCLOSE_BOX | wxRESIZE_BORDER);
	SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
	SetClientSize(300, 500);
    Centre();
#ifdef WIN32
	SetIcon(wxIcon("main", wxBITMAP_TYPE_ICO_RESOURCE));
#endif

    wxMenuBar* mb = new wxMenuBar;
    wxMenu* fileMenu = new wxMenu;
    mb->Append(fileMenu, wxT("&File"));
    {
        //fileMenu->Append(IDM_FILE_OPEN, wxT("&Open..."));
        fileMenu->Append(IDM_FILE_OPEN_DIRECTORY, wxT("Open &Folder..."));
        fileMenu->AppendSeparator();
		fileMenu->Append(IDM_FILE_PREFERENCES, wxT("&Preferences"));
        fileMenu->AppendSeparator();
        fileMenu->Append(IDM_FILE_EXIT, wxT("E&xit"));
    }

    wxMenu* debugMenu = new wxMenu;
    mb->Append(debugMenu, wxT("&Debug"));
    {
        debugMenu->Append(IDM_DEBUG_TEST1, wxT("Test1"));
        debugMenu->Append(IDM_DEBUG_TEST2, wxT("Test2"));
    }

    wxMenu* helpMenu = new wxMenu;
    mb->Append(helpMenu, wxT("&Help"));
    {
        helpMenu->Append(IDM_HELP_ABOUT, wxT("About"));
    }

    SetMenuBar(mb);

    wxBoxSizer* sz = new wxBoxSizer(wxVERTICAL);
    SetSizer(sz);
	{
		wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
		sz->Add(topSizer, 0, wxEXPAND | wxBOTTOM, 2);

		symbolText_ = new SymbolTextCtrl(this, ID_SYMBOL_TEXT);
		topSizer->Add(symbolText_, 3, wxEXPAND | wxRIGHT, 1);

		memberText_ = new SymbolTextCtrl(this, ID_MEMBER_TEXT);
		topSizer->Add(memberText_, 2, wxEXPAND);

		tree_ = new ClassTree(this, ID_CLASS_TREE);
		sz->Add(tree_, 1, wxEXPAND);
    }

	timer_.SetOwner(this);
	timer_.Start(100);


	std::string preferencesPath = static_cast<const char*>(wxStandardPaths::Get().GetUserDataDir().utf8_str());

	preferencesPath += Files::PATH_SEPARATOR;
	preferencesPath += "preferences.ta";
	PreferencesMaster::the().setFileName(preferencesPath.c_str());


	loadProject();
}

void MainFrame::onFileOpen(wxCommandEvent& event)
{
	tree_->rebuild();
	tagStorage.flushTags();
}


void MainFrame::onFileOpenDirectory(wxCommandEvent& event)
{
	wxDirDialog dialog(this, wxT("Select a project folder"), wxGetCwd(),
					   wxDD_DIR_MUST_EXIST | wxRESIZE_BORDER, wxDefaultPosition,
					   wxSize(500, 600));
	int result = dialog.ShowModal();
	if(result == wxID_OK){
		tagStorage.clear();
		tagStorage.addSource(new TagSourceDirectory(&tagStorage, dialog.GetPath().utf8_str()));
	}

	tree_->rebuild();
	tagStorage.flushTags();
}

void MainFrame::onFilePreferences(wxCommandEvent& event)
{
	Serializer ser(PreferencesMaster::the());
	wxPropertyDialog dialog(this, wxT("Preferences"), ser);
	dialog.ShowModal();
}

void MainFrame::onFileExit(wxCommandEvent& event)
{
    Close();
}

static const char* fileNames[] = {
    "c:\\dev\\vimkick\\libs\\property-tree\\PropertyItem.cpp",
    "c:\\dev\\vimkick\\libs\\property-tree\\PropertyItem.h"
};

void MainFrame::onDebugTest1(wxCommandEvent& event)
{
    tagStorage.clear();
    TagSourceFileList* source = new TagSourceFileList(&tagStorage);
    source->add(fileNames[0]);
    source->add(fileNames[1]);
    source->remove(fileNames[1]);
    //source->remove(fileNames[1]);
    tagStorage.addSource(source);
    tree_->rebuild();
    tagStorage.flushTags();
}

void MainFrame::onDebugTest2(wxCommandEvent& event)
{
    if(tagStorage.sources().empty())
        return;
    if( TagSourceFileList* source = dynamic_cast<TagSourceFileList*>(tagStorage.sources()[0].get()) )
    {
        source->add(fileNames[1]);
    }
}

void MainFrame::onHelpAbout(wxCommandEvent& event) {
    wxMessageBox(wxT("VimKick ") wxT(VIM_KICK_VERSION)
				 wxT("\nCopyright (c) 2008 Eugene <admix> Andreeshchev")
				 wxT("\nOn the terms of GNU GPL 2.0 (see LICENSE.txt)")
				 wxT("\nhttp://code.google.com/p/vimkick"),
                 wxT("About VimKick"), wxOK | wxICON_INFORMATION);
}

void MainFrame::poll()
{
	if(tagStorage.pollSources()){
		tree_->update();
		tagStorage.flushTags();
	}
}

void MainFrame::onIdle(wxIdleEvent& event)
{
	poll();
}

void MainFrame::onTimer(wxTimerEvent& event)
{
	//poll();
}

void MainFrame::onClose(wxCloseEvent& event)
{
	PreferencesMaster::the().save();
	saveProject();
    Destroy();
}

void MainFrame::onSymbolTextChanged(wxCommandEvent& event)
{
	tree_->setFilter(symbolText_->GetValue().utf8_str(), memberText_->GetValue().utf8_str());
}

void MainFrame::onSymbolTextEnter(wxCommandEvent& event)
{
	 if(const Tag* tag = tree_->selectedTag())
		editorPreferences.executeEditor(*tag);
}

void MainFrame::onSymbolSelected(wxTreeEvent& event)
{
	if(const Tag* tag = tree_->selectedTag()){
		editorPreferences.executeEditor(*tag);
	}
}


void MainFrame::serialize(Archive& ar)
{
    ASSERT(tree_);

	if(!ar.isEdit()){
		ar(*symbolText_, "symbolText");
		ar(*memberText_, "memberText");
		ar(*tree_, "tree");
		ar(static_cast<wxWindow&>(*this), "window");

		wxWindow* focusedWindow = FindFocus();
		int focusID = focusedWindow ? focusedWindow->GetId() : ID_SYMBOL_TEXT; 
		ar(focusID, "focusID");
		if(ar.isInput()){
			if(focusedWindow = FindWindowById(focusID))
				focusedWindow->SetFocus();
		}
	}
}

void MainFrame::commandFocus()
{
	Show();
	Raise();
#ifndef WIN32
	RequestUserAttention();
#endif
	symbolText_->SetFocus();
}

void MainFrame::loadProject()
{
	std::string path = static_cast<const char*>(wxStandardPaths::Get().GetUserDataDir().utf8_str());
	path += Files::PATH_SEPARATOR;
	path += "project.ta";
	if(Files::exists(path.c_str())){
		TextIArchive ia;
		ia.open(path.c_str());
		tagStorage.serialize(ia);
	}

	tree_->rebuild();
}

void MainFrame::saveProject()
{
	std::string path = static_cast<const char*>(wxStandardPaths::Get().GetUserDataDir().utf8_str());
	path += Files::PATH_SEPARATOR;
	path += "project.ta";
	TextOArchive oa(120);

	Files::createDirectoryForFile(path.c_str());
	oa.open(path.c_str());
	tagStorage.serialize(oa);
}

// ---------------------------------------------------------------------------

static const wxCmdLineEntryDesc cmdLineDesc[] =
{
	{ wxCMD_LINE_SWITCH, wxT("f"), wxT("focus"), wxT("activate window and focus symbol field") },
//	{ wxCMD_LINE_PARAM,  0, 0, "input file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE },

	{ wxCMD_LINE_NONE }
};

class VimKickConnection : public wxConnection{
public:
	VimKickConnection(MainFrame* mainFrame)
	: mainFrame_(mainFrame)
	{
	}
	bool OnExecute(const wxString& topic, wxChar* data, int size, wxIPCFormat format)
	{
		wxCmdLineParser parser(cmdLineDesc);
		parser.SetCmdLine(wxString(data, wxConvUTF8));
		std::cout << "Command line received: " << data << std::endl;

		if(parser.Parse(true) == 0){
			if(parser.Found(wxT("focus"))){
				std::cout << "got command focus" << std::endl;
				mainFrame_->commandFocus();
			}
			return true;
		}
		return false;
	}
protected:
	MainFrame* mainFrame_;
};

class VimKickServer : public wxServer{
public:
	VimKickServer()
	: mainFrame_(0)
	{
	}

	virtual wxConnectionBase * OnAcceptConnection(const wxString& topic)
	{
		std::cout << "Accepting connection with topic " << topic.utf8_str() << std::endl;
		return new VimKickConnection(mainFrame_);
	}

	void setMainFrame(MainFrame* frame) { mainFrame_ = frame; }		
protected:
	MainFrame* mainFrame_;

};



class VimKickClient : public wxClient{
public:
    VimKickClient()
	{
	}
	wxConnectionBase* OnMakeConnection() { return new VimKickConnection(0); }
};

// ---------------------------------------------------------------------------

class VimKickApp : public wxApp{
public:
    bool OnInit();
    int OnRun();
    int OnExit();

    DECLARE_CLASS(VimKickApp)
private:
    MainFrame* frame_;
	VimKickServer server_;
};

static wxString getIPCSocket()
{
	static wxString socketName;
	if(socketName.empty())
		socketName.Printf(_T("/tmp/.vimkick-%s.ipc"), wxGetUserId().c_str());
	return socketName;
}

bool VimKickApp::OnInit()
{
	frame_ = 0;
	SetAppName(wxT("VimKick"));
	wxString topic(wxT("VimKick"));
	wxString host = wxEmptyString;
	wxString service(getIPCSocket());

	VimKickClient client;
	//if(client.ValidHost(wxT("localhost"))){
		wxLogNull noLog;
		VimKickConnection* connection = (VimKickConnection*)client.MakeConnection(host, service, topic);
		if(connection){
			if(argc > 1){
				wxString fullCommandLine;
				for(int i = 1; i < argc; ++i){
					if(!fullCommandLine.empty())
						fullCommandLine += wxT(" ");
					fullCommandLine += wxT("\"");
					fullCommandLine += argv[i];
					fullCommandLine += wxT("\"");
				}
				std::cout << "Sending command: " << fullCommandLine.utf8_str() << std::endl;;
				if(!connection->Execute(fullCommandLine))
					std::cout << "ERROR: Unable to send command";
			}
			else{
				std::cout << "Sending command: --focus" << std::endl;;
				if(!connection->Execute(wxT("--focus")))
					std::cout << "ERROR: Unable to send command";
			}
			return false;
		}
		else{
			std::cout << "Unable to make connection to localhost" << std::endl;
			//return false;
		}
		/*
	}
	else{
		std::cout << "Error: localhost seems to be invalid host!" << std::endl;
		return false;
	}
	*/

	//wxLog::SetActiveTarget(oldLog);

	/*
		 std::string socketPath = wxStandardPaths::Get().GetUserDataDir().utf8_str();
		 socketPath += Files::PATH_SEPARATOR;
		 socketPath += ".socket";
		 */

	if(!server_.Create(service)){
		wxMessageBox(wxT("Error: unable to start DDE server."), wxT("error"), wxOK | wxICON_ERROR);
		return false;
	}

	if(tagPreferences.ctagsExecutable().empty()){
		wxMessageBox(wxT("Error: unable to locate ctags executable."), wxT("error"), wxOK | wxICON_ERROR);
		return false;
	}

	try{
		frame_ = new MainFrame;
		server_.setMainFrame(frame_);
		frame_->Show();
		SetTopWindow(frame_);

	}
	catch(ErrorGeneric& err){
		handleException(err);
		return false;
	}
	return true;
}


int VimKickApp::OnRun()
{
    try{

		if(frame_){
			wxIdleEvent event;
			event.SetEventObject(frame_);
			frame_->AddPendingEvent(event);
		}

        return wxApp::OnRun();
    }
    catch(ErrorGeneric& err){
        handleException(err);
        return -1;
    }
    return 0;
}

int VimKickApp::OnExit()
{
    return wxApp::OnExit();
}

IMPLEMENT_CLASS(VimKickApp, wxApp)
IMPLEMENT_APP(VimKickApp)
